#include "../kernel.h"

struct acpi_rsdp_t
{
	 uint8_t	signature[8];	/* ``RSD PTR '' */
	 uint8_t	checksum;
	 uint8_t	oem_id[6];
	 uint8_t	revision;
	 physaddr_t rsdt_phys;
	 uint32_t	rsdt_len;
	 uint64_t	xrsdt_phys;
	 uint8_t	xchecksum;
	 uint8_t	__reserved[3];
} __attribute__((packed));

/* standard table header structure */
struct acpi_sdth_t
{
	 uint8_t	signature[4];
	 uint32_t	length;
	 uint8_t	revision;
	 uint8_t	checksum;
	 uint8_t	oem_id[6];
	 uint64_t	oem_table_id;
	 uint32_t	oem_revision;
	 uint32_t	creator_id;
	 uint32_t	creator_revision;
} __attribute__((packed));

struct acpi_gas_t
{
	 uint8_t addr_space_id;
	 uint8_t reg_width;
	 uint8_t reg_offset;
	 uint8_t __reserved;
	 union
	 {
		  uint32_t addr_32;
		  uint64_t addr_64;
	 };
} __attribute__((packed));

struct acpi_hpet_desc_t
{
	 uint32_t block_id;
	 struct acpi_gas_t base_low_addr;
	 uint8_t  hpet_num;
	 uint16_t mc_priod_min_tick;
	 uint8_t  pp_oem;
} __attribute__((packed));

struct acpi_madt_t
{
	 physaddr_t lapic_phys;
	 uint32_t flags;
};

struct acpi_madt_apic_desc_t
{
	 uint8_t type;
	 uint8_t length;
	 union
	 {
		  struct
		  {
			   uint8_t proc_id;
			   uint8_t apic_id;
			   uint8_t flags;
		  } __attribute__((packed)) lapic;

		  struct
		  {
			   uint8_t apic_id;
			   uint8_t reserved;
			   physaddr_t phys;
			   uint32_t intr_base;
		  } __attribute__((packed)) ioapic;
	 };
} __attribute__((packed));

static uint8_t
sum(uint8_t *addr, int len)
{
	 int i, sum;
	 sum = 0;
	 for(i = 0; i < len; i++)
		  sum += addr[i];
	 return sum;
}

static struct acpi_rsdp_t*
acpi_rsdp_search_segment(uint8_t *addr, int len, uint32_t align, int ex)
{
	 uintptr_t e, p;
	 struct acpi_rsdp_t *result = NULL;

	 e = (uintptr_t)(addr + len) & ~align;
	 p = (uintptr_t)addr;
	 if (p & align) p = (p | align) + 1;
	 for(; p < e; p = p + align + 1)
		  if(memcmp((char *)p, "RSD PTR ", 8) == 0)
		  {
			   if (sum((char *)p, ex ? 36 : 20) == 0)
			   {
					result = (struct acpi_rsdp_t*)p;
					return result;
			   }
		  }
	 return NULL;
}

#define BDA 0x400

// Search for the RSDP, which according to the
// spec is in one of the following three locations:
// 1) in the first KB of the EBDA;
// 2) in the BIOS ROM between 0xE0000 and 0xFFFFF.
static struct acpi_rsdp_t*
acpi_rsdp_search(void)
{
	 uint8_t *bda;
	 uint32_t p;
	 struct acpi_rsdp_t *rsdp;

	 bda = (uint8_t *)KADDR(BDA);
	 p = (uint32_t)KADDR(((bda[0x0F] << 8) | bda[0x0E]) << 4);
	 if (p)
	 {
		  if ((rsdp = acpi_rsdp_search_segment((uint8_t *)p, 1024, 0xf, 1)))
			   return rsdp;
	 }
	 return acpi_rsdp_search_segment((uint8_t *)KADDR(0xE0000), 0x20000, 0xf, 1);
}

int
acpi_init(void)
{
	 struct acpi_rsdp_t *rsdp = acpi_rsdp_search();
	 if (!rsdp)
	 {
		  kprintf("NO RSDP IN MEMORY\n");
		  return -E_UNSUPPORTED;
	 }

	 initial_conf.lcpu_count = 0;
	 initial_conf.ioapic_count = 0;
	 initial_conf.has_hpet = 0;

	 uint32_t b;
	 cpuid(1, NULL, &b, NULL, NULL);
	 int cur_apic_id = (b >> 24) & 0xff;

	 struct acpi_sdth_t *rsdt = (struct acpi_sdth_t *)KADDR(rsdp->rsdt_phys);
	 int n = (rsdt->length  - sizeof(struct acpi_sdth_t)) / 4;
	 physaddr_t *phys = (physaddr_t *)(rsdt + 1);
	 int i;
	 char sign[5];
	 sign[4] = 0;
	 for (i = 0; i != n; ++ i)
	 {
		  struct acpi_sdth_t *cur = (struct acpi_sdth_t *)KADDR(phys[i]);
		  memmove(sign, (char *)&cur->signature, 4);
		  kprintf("Processing %s\n", sign);
		  if (memcmp(cur->signature, "APIC", 4) == 0)
		  {
			   struct acpi_madt_t *madt = (struct acpi_madt_t *)(cur + 1);

			   initial_conf.lapic_phys = madt->lapic_phys;

			   char *apic_cur = (char *)(madt + 1);
			   char *apic_end = (char *)cur + cur->length;

			   while (apic_cur < apic_end)
			   {
					struct acpi_madt_apic_desc_t *desc =
						 (struct acpi_madt_apic_desc_t *)apic_cur;

					if (desc->type == 0 && (desc->lapic.flags & 1))
					{
						 /* Logical Processor, enabled */
						 lcpus[initial_conf.lcpu_count].apic_id = desc->lapic.apic_id;
						 if (cur_apic_id == desc->lapic.apic_id)
						 {
							  initial_conf.lcpu_boot = initial_conf.lcpu_count;
						 }
						 apic_inv_map[desc->lapic.apic_id] = initial_conf.lcpu_count;
						 ++ initial_conf.lcpu_count;
					}
					else if (desc->type == 1)
					{
						 /* IO APIC */
						 ioapics[initial_conf.ioapic_count].apic_id = desc->ioapic.apic_id;
						 ioapics[initial_conf.ioapic_count].phys = desc->ioapic.phys;
						 ioapics[initial_conf.ioapic_count].intr_base = desc->ioapic.intr_base;
						 ++ initial_conf.ioapic_count;
					}

					apic_cur += desc->length;
			   }
		  }
		  else if (memcmp(cur->signature, "HPET", 4) == 0)
		  {
			   struct acpi_hpet_desc_t *hpet = (struct acpi_hpet_desc_t *)(cur + 1);
			   initial_conf.has_hpet = 1;
			   initial_conf.hpet_phys = hpet->base_low_addr.addr_32;
		  }
	 }

	 return 0;
}
